﻿/*
 * To use of this class, You should agree Seoul Open API's terms of use 
 * - You cannot use API services commercialy by the terms of use
 * - http://mobile.openapi.seoul.go.kr/sayong/main.RightRelations.do
 */
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections;
using System.Collections.Generic;
using Example_SeoulOpenAPI.ViewModels;
using System.Diagnostics;
using System.Xml.Linq;
using System.Linq;
using System.Collections.ObjectModel;
using Example_SeoulOpenAPI.Commons;

namespace Example_SeoulOpenAPI.Services
{
	/*
	 * SOAP References 
	 * - Wikipedia : http://en.wikipedia.org/wiki/SOAP
	 * - W3C Note(SOAP1.1) : http://www.w3.org/TR/2000/NOTE-SOAP-20000508
	 */

	public class StationService
	{
		#region ID/KEY

        // 인증키 발급: http://mobile.openapi.seoul.go.kr/
        private static string USER_ID = Credential.USER_ID;
        private static string AUTH_KEY = Credential.AUTH_KEY;

		#endregion

		// Request Uris
		private static string REQUEST_URI_STATION_LIST_BY_LINE_NUM = "http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayStationService?wsdl";
		private static string REQUEST_URI_ARRIVAL_INFO_BY_FRCODE = "http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayArriveService?wsdl";
		private static string REQUEST_URI_TRAIN_STORY = "http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayArriveService?wsdl";

		// Returning data (with event)
		public static ObservableCollection<Station> Stations { get; private set; }
		public static ObservableCollection<Arrival> Arrivals { get; private set; }
		public static ObservableCollection<PassingStation> PassingStations { get; private set; }
		private static Exception errorStatus;

		#region DownloadStationsByLineNumAsync

		/// <summary>
		/// 각 호선에 해당하는 역사 정보를 받아옴 / retrieves station lists for lineNum
		/// </summary>
		/// <param name="lineNum">1~9(호선), J(중앙선), A(공항철도), B(분당선), G(경춘선), K(경의선), I(인천1호선)</param>
		public static void DownloadStationsByLineNumAsync(string lineNum)
		{
			WebClient stationListDownloader = new WebClient();
			string requestPayload = BuildSoapEnvelopeForStaionListByLine(lineNum);
			errorStatus = null;

			Debug.WriteLine("[StationService] DownloadStationByLineNumAsync: requesting for station list data ... ");
			AddSoapHeaders(stationListDownloader);
			stationListDownloader.UploadStringCompleted += new UploadStringCompletedEventHandler(stationListDownloader_UploadStringCompleted);
			stationListDownloader.UploadStringAsync(new Uri(REQUEST_URI_STATION_LIST_BY_LINE_NUM, UriKind.Absolute), "POST", requestPayload);
		}

		static void stationListDownloader_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
		{
			Debug.WriteLine("[StationService] stationListDownloader_UploadStringCompleted: downloaded");

			if (e.Error != null)
			{
				// DOWNLOAD FAILED
				Debug.WriteLine("[StationService] download failed: " + e.Error.Message);
				Stations = null;
				errorStatus = e.Error;
			}
			else
			{
				Debug.WriteLine("[StationService] download succeeded");
				string rawXmlData = e.Result;
				Stations = ParseRawXmlToStations(rawXmlData);
			}

			// generate event
			onStationsDownloadCompleted();
		}

		private static ObservableCollection<Station> ParseRawXmlToStations(string rawXmlData)
		{
			XNamespace ax250 = "http://vo.station.subway.openapi.ubizvalley.com/xsd";
			XName subwayStationListName = ax250 + "subwayStationListArrayVo";

			XDocument xDocStations = XDocument.Parse(rawXmlData);

			var stationsTemp = from s in xDocStations.Descendants(subwayStationListName)
							   select new Station
							   {
								   FrCode = s.Element(ax250 + "frCode").Value,
								   LineName = s.Element(ax250 + "lineName").Value,
								   LineNum = s.Element(ax250 + "lineNum").Value,
								   StationCode = s.Element(ax250 + "stationCd").Value,
								   StationName = s.Element(ax250 + "stationNm").Value
							   };

			return new ObservableCollection<Station>(stationsTemp);
		}

		#endregion

		#region DownloadArrivalsByFrCodeAsync

		/// <summary>
		/// 역외부코드를 이용해 도착정보 받아오기, 모두 완료시 Completed 이벤트를 발생
		/// Retrieves train arrival information of station that is specified from frCode. Generates Completed event when downloading/parsing completed
		/// </summary>
		/// <param name="frCode">역외부코드 / FrCode</param>
		/// <param name="directionCode">방향 - 1:상행/내선 / 2. 하행/외선 / Ditection - 1. North/Inner Circle / 2. South/Outer Circle</param>
		/// <param name="isLine2">2호선 데이터면 true, 아니면 false(기본값) / set this true when retrieving line2 data (false is default)</param>
		public static void DownloadArrivalsByFrCodeAsync(string frCode, string directionCode, bool isLine2 = false)
		{
			WebClient arrivalDownloader = new WebClient();
			string requestPayload = (isLine2 == false) ? BuildSoapEnvelopeForArrivalByFrCode(frCode, directionCode) : BuildSoapEnvelopeForLine2ArrivalByFrCode(frCode, directionCode);
			errorStatus = null;

			Debug.WriteLine("[StationService] BuildSoapEnvelopeForArrivalByFrCode: requesting for arrival list data ... ");
			AddSoapHeaders(arrivalDownloader);
			arrivalDownloader.UploadStringCompleted += new UploadStringCompletedEventHandler(arrivalDownloader_UploadStringCompleted);
			arrivalDownloader.UploadStringAsync(new Uri(REQUEST_URI_ARRIVAL_INFO_BY_FRCODE, UriKind.Absolute), "POST", requestPayload);

		}

		static void arrivalDownloader_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
		{
			Debug.WriteLine("[StationService] arrivalDownloader_UploadStringCompleted: downloaded");

			if (e.Error != null)
			{
				// DOWNLOAD FAILED
				Debug.WriteLine("[StationService] download failed: " + e.Error.Message);
				Stations = null;
				errorStatus = e.Error;
			}
			else
			{
				Debug.WriteLine("[StationService] download succeeded");
				string rawXmlData = e.Result;
				Arrivals = ParseRawXmlToArrivals(rawXmlData);
			}

			// generate event
			onArrivalsDownloadCompleted();
		}

		private static ObservableCollection<Arrival> ParseRawXmlToArrivals(string rawXmlData)
		{
			IEnumerable<Arrival> arrivalsTemp;

			XNamespace ax230 = "http://vo.arrive.subway.openapi.ubizvalley.com/xsd";
			XName subwayStationListName = ax230 + "subwayArriveListArrayVo";

			XDocument xDocStations = XDocument.Parse(rawXmlData);

			// determine if XML contains line2-only-tag
			if (rawXmlData.Contains("searchSubwayArriveLine2ListByFrCodeResponse"))
			{
				// LINE 2 
				arrivalsTemp = from s in xDocStations.Descendants(subwayStationListName)
							   select new Line2Arrival
							   {
								   FrCode = s.Element(ax230 + "frCode").Value,
								   StationCode = s.Element(ax230 + "stationCd").Value,
								   ArriveTime = s.Element(ax230 + "arriveTime").Value,
								   ArriveTrainCode = s.Element(ax230 + "arriveTranCode").Value,
								   DestinationCode = s.Element(ax230 + "deststationCode").Value,
								   DestinationName = s.Element(ax230 + "deststationName").Value,
								   NextStationCode = s.Element(ax230 + "arriveSubwayECode").Value,
								   NextStationName = s.Element(ax230 + "arriveSubwayEName").Value,
							   } as Arrival;
			}
			else
			{
				// Other lines
				arrivalsTemp = from s in xDocStations.Descendants(subwayStationListName)
							   select new Arrival
							   {
								   FrCode = s.Element(ax230 + "frCode").Value,
								   StationCode = s.Element(ax230 + "stationCd").Value,
								   ArriveTime = s.Element(ax230 + "arriveTime").Value,
								   ArriveTrainCode = s.Element(ax230 + "arriveTranCode").Value,
								   DestinationCode = s.Element(ax230 + "arriveSubwayECode").Value,
								   DestinationName = s.Element(ax230 + "arriveSubwayEName").Value,
							   };
			}

			return new ObservableCollection<Arrival>(arrivalsTemp);
		}

		#endregion

		#region DownloadTrainStoryAsync

		/// <summary>
		/// 열차 코드를 이용해 경유역 리스트 받아오기
		/// </summary>
		/// <param name="trainCode">열차코드</param>
		/// <param name="directionCode">1: 상행/내선순환, 2: 하행/외선순환 / 1: North/CW, 2: South/CCW</param>
		/// <param name="dayCode">1: 평일, 2: 토요일, 3: 일요일,공휴일 / 1: Weekdays, 2: Saturday, 3: Sunday+Holiday</param>
		public static void DownloadTrainStoryAsync(string trainCode, string directionCode, string dayCode)
		{
			WebClient trainStoryDownloader = new WebClient();

			string requestPayload = BuildSoapEnvelopeForTrainStory(trainCode, directionCode, dayCode);
			errorStatus = null;

			Debug.WriteLine("[StationService] DownloadTrainStoryAsync: requesting for passing station list data ... ");
			AddSoapHeaders(trainStoryDownloader);
			trainStoryDownloader.UploadStringCompleted += new UploadStringCompletedEventHandler(trainStoryDownloader_UploadStringCompleted);
			trainStoryDownloader.UploadStringAsync(new Uri(REQUEST_URI_TRAIN_STORY, UriKind.Absolute), "POST", requestPayload);
		}

		static void trainStoryDownloader_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
		{
			Debug.WriteLine("[StationService] trainStoryDownloader_UploadStringCompleted: downloaded");

			if (e.Error != null)
			{
				// DOWNLOAD FAILED
				Debug.WriteLine("[StationService] download failed: " + e.Error.Message);
				Stations = null;
				errorStatus = e.Error;
			}
			else
			{
				Debug.WriteLine("[StationService] download succeeded");
				string rawXmlData = e.Result;
				PassingStations = ParseRawXmlToPassingStations(rawXmlData);
			}

			// generate event
			onTrainStoryDownloadCompleted();
		}

		private static ObservableCollection<PassingStation> ParseRawXmlToPassingStations(string rawXmlData)
		{
			XNamespace ax230 = "http://vo.arrive.subway.openapi.ubizvalley.com/xsd";
			XName viaStationListArrayVo = ax230 + "viaStationListArrayVo";

			XDocument xDocPassingStations = XDocument.Parse(rawXmlData);
			var passingStations = from s in xDocPassingStations.Descendants(viaStationListArrayVo)
								  select new PassingStation
								  {
									  Destination = s.Element(ax230 + "arriveSubwayEName").Value,
									  ArriveTime = s.Element(ax230 + "arriveTime").Value,
									  DayCode = s.Element(ax230 + "dayGubun").Value,
									  DirectionCode = s.Element(ax230 + "forGubun").Value,
									  FrCode = s.Element(ax230 + "frCode").Value,
									  StationCode = s.Element(ax230 + "stationCd").Value,
									  StationName = s.Element(ax230 + "stationNm").Value
								  };

			return new ObservableCollection<PassingStation>(passingStations);
		}

		#endregion

		// ------------------------------------------------------------------------------------------

		#region SOAP Header and Envelope

		private static void AddSoapHeaders(WebClient stationListDownloader)
		{
			/* SOAP HTTP header specification & references : 
			 * - http://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383528
			 * - http://www.w3schools.com/soap/soap_httpbinding.asp
			 */

			stationListDownloader.Headers["SOAPAction"] = "http://www.w3.org/2003/05/soap-envelope";
			stationListDownloader.Headers["Content-Type"] = "application/soap+xml; charset=utf-8";
		}

		private static string BuildSoapEnvelopeForStaionListByLine(string lineNum)
		{
			string soapEnvelope =
				"<?xml version='1.0' encoding='utf-8'?>" +
				"<soapenv:Envelope xmlns:soapenv='http://www.w3.org/2003/05/soap-envelope'>" +
				  "<soapenv:Header>" +
					"<authInfo:userId xmlns:authInfo='http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayStationService?wsdl'>{0}</authInfo:userId>" +
					"<authInfo:authKey xmlns:authInfo='http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayStationService?wsdl'>{1}</authInfo:authKey>" +
				  "</soapenv:Header>" +
				  "<soapenv:Body>" +
					"<ns5:searchSubwayStationListByLineNum xmlns:ns5='http://service.station.subway.openapi.ubizvalley.com'>" +
					  "<ns5:lineNum>{2}</ns5:lineNum>" +
					  "<ns5:sortItem />" +
					  "<ns5:sortStd />" +
					  "<ns5:cPage>1</ns5:cPage>" +
					  "<ns5:pageSize>100</ns5:pageSize>" +
					"</ns5:searchSubwayStationListByLineNum>" +
				  "</soapenv:Body>" +
				"</soapenv:Envelope>";

			return String.Format(soapEnvelope, USER_ID, AUTH_KEY, lineNum);
		}

		/// <summary>
		///  FrCode를 이용해 도착정보를 받아오기 위한 SOAP 데이터를 조립 / Builds SOAP envelope for retrieving arrival info with FrCode
		/// </summary>
		/// <param name="frCode">역외부코드 / FrCode</param>
		/// <param name="directionCode">방향 - 1:상행/내선 / 2. 하행/외선 / Ditection - 1. North/Inner Circle / 2. South/Outer Circle </param>
		/// <returns></returns>
		private static string BuildSoapEnvelopeForArrivalByFrCode(string frCode, string directionCode)
		{
			string soapEnvelope =
				"<?xml version='1.0' encoding='utf-8'?>" +
				"<soapenv:Envelope xmlns:soapenv='http://www.w3.org/2003/05/soap-envelope'>" +
					"<soapenv:Header>" +
						"<authInfo:userId xmlns:authInfo='http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayArriveService?wsdl'>{0}</authInfo:userId>" +
						"<authInfo:authKey xmlns:authInfo='http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayArriveService?wsdl'>{1}</authInfo:authKey>" +
					"</soapenv:Header>" +
					"<soapenv:Body>" +
						"<ns3:searchSubwayArriveListByFrCode xmlns:ns3='http://service.arrive.subway.openapi.ubizvalley.com'>" +
							"<ns3:frCode>{2}</ns3:frCode>" +
							"<ns3:forGubun>{3}</ns3:forGubun>" +
						"</ns3:searchSubwayArriveListByFrCode>" +
					"</soapenv:Body>" +
				"</soapenv:Envelope>";

			return String.Format(soapEnvelope, USER_ID, AUTH_KEY, frCode, directionCode);
		}

		private static string BuildSoapEnvelopeForLine2ArrivalByFrCode(string frCode, string directionCode)
		{
			string soapEnvelope =
				"<?xml version='1.0' encoding='utf-8'?>" +
				"<soapenv:Envelope xmlns:soapenv='http://www.w3.org/2003/05/soap-envelope'>" +
					"<soapenv:Header>" +
						"<authInfo:userId xmlns:authInfo='http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayArriveService?wsdl'>{0}</authInfo:userId>" +
						"<authInfo:authKey xmlns:authInfo='http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayArriveService?wsdl'>{1}</authInfo:authKey>" +
					"</soapenv:Header>" +
					"<soapenv:Body>" +
						"<ns3:searchSubwayArriveLine2ListByFrCode xmlns:ns3='http://service.arrive.subway.openapi.ubizvalley.com'>" +
							"<ns3:frCode>{2}</ns3:frCode>" +
							"<ns3:forGubun>{3}</ns3:forGubun>" +
						"</ns3:searchSubwayArriveLine2ListByFrCode>" +
					"</soapenv:Body>" +
				"</soapenv:Envelope>";

			return String.Format(soapEnvelope, USER_ID, AUTH_KEY, frCode, directionCode);
		}

		private static string BuildSoapEnvelopeForTrainStory(string trainCode, string directionCode, string dayCode)
		{
			string soapEnvelope =
				"<?xml version='1.0' encoding='utf-8'?>" +
				"<soapenv:Envelope xmlns:soapenv='http://www.w3.org/2003/05/soap-envelope'>" +
					"<soapenv:Header>" +
						"<authInfo:userId xmlns:authInfo='http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayArriveService?wsdl'>{0}</authInfo:userId>" +
						"<authInfo:authKey xmlns:authInfo='http://mobile.openapi.seoul.go.kr/SeoulOpenAPI/services/SubwayArriveService?wsdl'>{1}</authInfo:authKey>" +
					"</soapenv:Header>" +
					"<soapenv:Body>" +
						"<ns3:searchViaStationListByTrainCode xmlns:ns3='http://service.arrive.subway.openapi.ubizvalley.com'>" +
							"<ns3:trainCode>{2}</ns3:trainCode>" +
							"<ns3:forGubun>{3}</ns3:forGubun>" +
							"<ns3:dayGubun>{4}</ns3:dayGubun>" +
						"</ns3:searchViaStationListByTrainCode>" +
					"</soapenv:Body>" +
				"</soapenv:Envelope>";

			return String.Format(soapEnvelope, USER_ID, AUTH_KEY, trainCode, directionCode, dayCode);
		}

		#endregion

		// ------------------------------------------------------------------------------------------

		#region events

		public delegate void StationsDownloadStatus(Exception errorStatus, ObservableCollection<Station> stations);
		public static event StationsDownloadStatus StationDownloadCompleted;
		protected static void onStationsDownloadCompleted()
		{
			StationDownloadCompleted(errorStatus, Stations);
		}

		public delegate void ArrivalDownloadStatus(Exception errorStatus, ObservableCollection<Arrival> arrivals);
		public static event ArrivalDownloadStatus ArrivalDownloadCompleted;
		protected static void onArrivalsDownloadCompleted()
		{
			ArrivalDownloadCompleted(errorStatus, Arrivals);
		}

		public delegate void TrainStoryDownloadStatus(Exception errorStatus, ObservableCollection<PassingStation> passingStation);
		public static event TrainStoryDownloadStatus TrainStoryDownloadCompleted;
		protected static void onTrainStoryDownloadCompleted()
		{
			TrainStoryDownloadCompleted(errorStatus, PassingStations);
		}

		#endregion

	}
}

